/*
 * Document.java
 * Describe SWF
 *
 * A browser for Flash files.
 * Copyright (c) 2001-2006 Flagstone Software Ltd. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 * 
 * For more information please contact:
 * 
 * Stuart MacKay
 * Flagstone Software Ltd
 * 92 High Street
 * Wick, Caithness KW1 4LY
 * Scotland
 * 
 * www.flagstonesoftware.com
 */
package com.flagstone.describe;

import java.util.*;
import java.util.zip.Inflater;
import java.util.zip.DataFormatException;
import java.io.*;
import java.net.URL;
import java.awt.*;

import javax.swing.JOptionPane;

class Document extends java.util.Observable implements MovieListener
{
    private static final char[] hexChars = {
        '0','1','2','3','4','5','6','7',
        '8','9','A','B','C','D','E','F'
        };

    private final static int xDelta = 25;
    private final static int yDelta = 25;

    private static int xOffset = 0;
    private static int yOffset = 0;

    private static int numberOfDocuments = 1;    
    private static Hashtable documents = new Hashtable();

    static final OpenPanel swfPanel = new OpenPanel(new String[] {".swf"}, App.getInstance().localizedString("Chooser.FlashFiles"));
    static final OpenPanel txtPanel = new OpenPanel(new String[] {".txt"}, App.getInstance().localizedString("Chooser.FlashFiles"));
    
    static synchronized String newTitle()
    {
        return App.getInstance().localizedString("Document.Untitled") + numberOfDocuments++;
    }    

    static void newDocument()
    {
        Document doc = new Document(App.getInstance().getIcon(), newTitle());
        documents.put(doc.identifier, doc);
        doc.show();
    }
    static void removeDocument(Integer identifier)
    {
        if (documents.containsKey(identifier)) 
        {
            documents.remove(identifier);

            if (documents.size() == 0) {
                App.sharedInstance.quit();        
            }
        }        
    }

    private Integer identifier = null;
    
    private DocumentMenu menu = null;
    private DocumentWindow window = null;

    private File swfFile = null;
    private byte[] fileData = null;
    private char[] charData = new char[40];
    
    private MovieNode nodes = null;
    private MovieNode currentNode = null;

    Document(Image icon, String title)
    {
        identifier = new Integer(title.hashCode());

        nodes = new MovieNode(null, true, -1, "movie", 0, 0, "FSMovie");
        currentNode = nodes;

        menu = new DocumentMenu(this);
       
        window = new DocumentWindow(icon, this, nodes);
        window.setTitle(title);
        window.setJMenuBar(menu);
    }

    MovieNode getNodes()
    {
        return nodes;
    }
    byte[] getFileData()
    {
        return fileData;
    }
    
    void show() 
    {
        Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
        
        int x = (screen.width-window.getWidth()+xOffset)/2;
        int y = (screen.height-window.getHeight()+yOffset)/2;

        xOffset += xDelta;
        yOffset += yDelta;
        
        window.setLocation(x, y);
        window.setVisible(true);
        window.toFront();
    }
    void hide()
    {
        window.setVisible(false);
        window.hide();
    }
    void open()
    {
        File file = swfPanel.open();

        if (file != null)
        {
            Integer id = new Integer(file.hashCode());
            
            if (documents.containsKey(id))
            {
                ((Document)documents.get(id)).show();
            }
            else
            {
                try
                {
                    if (file.exists() == false)
                        throw new Exception(App.getInstance().localizedString("Error.FileNotFound", file.getName()));

                    if (file.canRead() == false)
                        throw new Exception(App.getInstance().localizedString("Error.CannotOpenFile", file.getName()));

                    if (swfFile == null) // untitled document
                    {
                        documents.remove(identifier);
                        readFromFile(file);  
                        documents.put(identifier, this);
                        window.toFront(); // calling show() will reposition the window
                    }
                    else
                    {
                        Document controller = new Document(App.getInstance().getIcon(), file.getName());
                        controller.readFromFile(file);                                                
                        documents.put(controller.identifier, controller);
                        controller.show();                        
                    }
                }
                catch(IOException e)
                {
                    JOptionPane.showMessageDialog(null, e.getMessage(), null, JOptionPane.ERROR_MESSAGE);
                }
                catch (Exception e)
                {
                    JOptionPane.showMessageDialog(null, e.getMessage(), null, JOptionPane.ERROR_MESSAGE);
                }
            }         
        }
    }
    void close()
    {
        window.hide();
        window.dispose();

        removeDocument(identifier);
    }
    void export()
    {
        String fileName = txtPanel.saveAs();
        
        if (fileName != null)
        {
            if (fileName.endsWith(".txt") == false)
                fileName = fileName + ".txt";
            
            File file = new File(fileName);

            try
            {
                FileOutputStream stream = new FileOutputStream(file);
                PrintWriter pw = new PrintWriter(stream);
                
                MovieNode.describeNode(nodes, pw, 0);
                
                pw.close();
                stream.close();
            }
            catch(IOException e)
            {                
                JOptionPane.showMessageDialog(null, e.getMessage(), null, JOptionPane.ERROR_MESSAGE);
            }
        }
    }
    void quit()
    {
        App.sharedInstance.quit();        
    }
    
    void readFromFile(File file) throws IOException
    {
        identifier = new Integer(file.hashCode());
        window.setTitle(file.getName());

        swfFile = file;
        
        loadFlashFile(file);
        
        try
        {
            if (fileData[0] == 0x43)
            {
                int movieLength = 0;
    
                for (int i=0; i<4; i++)
                    movieLength += (fileData[i+4] & 0x000000FF) << (i*8);
                    
                byte[] unzippedBytes = new byte[movieLength];
                
                for (int i=0; i<8; i++)
                    unzippedBytes[i] = fileData[i];            
                
                Inflater inflater = new Inflater();
                inflater.setInput(fileData, 8, fileData.length-8);
                inflater.inflate(unzippedBytes, 8, movieLength-8);
                
                fileData = unzippedBytes;
            }
        }
        catch (DataFormatException e)
        {            
        }
        
        SWF.decodeMovie(fileData, this);
        
        menu.enableExport(true);

        setChanged();
        notifyObservers(nodes);
    }

    private void loadFlashFile(File file) throws IOException
    {
        int fileLength = (int)file.length();
        int bytesRead = 0;
        
        fileData = new byte[fileLength];
        
        for (int i=0; i<fileData.length; i++)
            fileData[i] = 0;
        
        FileInputStream fileStream = new FileInputStream(file);
        bytesRead = fileStream.read(fileData);
        fileStream.close();
        
        if (bytesRead != fileLength)
            throw new IOException();
    }
    
    public String describeNode(MovieNode aNode)
    {
        StringBuffer buffer = new StringBuffer();
        MovieNode.describeNode(aNode, buffer, 0);
        return buffer.toString();
    }
    public URL infoForNode(MovieNode node)
    {
        URL page = null;

        String nodeType = node.getUserObject().toString();
        
        if (node.isLeaf())
        {
            node = (MovieNode)node.getParent();
            nodeType = node.getUserObject().toString();
        }
            
        if (nodeType.indexOf('_') != -1)
            nodeType = ((MovieNode)node.getParent()).getUserObject().toString();
            
        page = getClass().getResource("/descriptions/" + nodeType + ".html");

        return page;
    }
    public void recordMovieEvent(int event, String name, int location, int length, Object value)
    {
        int index = currentNode.isArray() ? currentNode.getChildCount() : -1;
            
        switch (event)
        {
            case MovieListener.Begin:
                MovieNode node = new MovieNode(currentNode, (value.toString().equals("Array")), index, name, location, length, value);
                currentNode.add(node);
                currentNode = node;
                break;
            case MovieListener.End:
                currentNode.setLength(location - currentNode.getLocation());
                currentNode.setUserObject(value);
                
                if (currentNode.getParent() != null)
                    currentNode = (MovieNode)currentNode.getParent();
                else
                    currentNode = nodes;
                break;
            case MovieListener.Value:
                currentNode.add(new MovieNode(currentNode, false, index ,name, location, length, value));
                break;
            default:
                break;
        }
    }

    public String binaryString(int offset, int length, int spacing)
    {
        int count = (length < fileData.length-offset) ? length : fileData.length-offset;
        int index = 0;

        for (int i=0; i<count; i++)
        {
            int value = fileData[offset+i] & 0x00FF;
            
            for (int j=0; j<8; j++)
            {
                charData[index++] = ((value & 0x80 >>> j) != 0) ? '1' : '0';
                
                if (j%spacing == spacing-1)
                    charData[index++] = ' ';
            }
        }
        return new String(charData, 0, charData[index-1] == ' ' ? index-1 : index);
    }
    public String hexString(int offset, int length, int spacing)
    {
        int count = (length < fileData.length-offset) ? length : fileData.length-offset;
        int index = 0;

        for (int i=0; i<count; i++)
        {
            int value = fileData[offset+i] & 0x00FF;
            
            charData[index++] = hexChars[(value>>4)&0x0F];
            charData[index++] = hexChars[value&0x0F];
            
            if (i%spacing == spacing-1)
                charData[index++] = ' ';
        }
        return new String(charData, 0, charData[index-1] == ' ' ? index-1 : index);
    }
    public String characterString(int offset, int length)
    {
        int count = (length < fileData.length-offset) ? length : fileData.length-offset;

        for (int i=0; i<count; i++)
        {
            char c = (char)(fileData[offset+i] & 0x00FF);
            
            if (c == '\t')
                c = '.';
            
            charData[i] = c;
        }

        return new String(charData, 0, count);
    }
}